package com.asset.dispoal.file.evidenceRecognition.service.commom;

import com.alibaba.fastjson2.JSONObject;
import com.asset.dispoal.business.bsEvidenceRecognition.domain.redis.dto.RedisMatchDTO;
import com.asset.dispoal.file.evidenceRecognition.domain.db.entity.EvidenceRecognition;
import com.asset.dispoal.file.evidenceRecognition.domain.db.mapper.EvidenceRecognitionMapper;
import com.asset.dispoal.file.evidenceRecognition.domain.dto.AliasCodeDTO;
import com.asset.dispoal.file.evidenceRecognition.domain.dto.EvidenceFileProDto;
import com.asset.dispoal.file.evidenceRecognition.dto.EvidenceRecognitionProRequest;
import com.asset.dispoal.file.evidenceRecognition.dto.RecognitionConfigRequest;
import lombok.extern.slf4j.Slf4j;
import org.apache.tools.zip.ZipEntry;
import org.apache.tools.zip.ZipFile;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.core.env.Environment;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import tech.mhuang.pacebox.core.ddd.BaseCommand;
import tech.mhuang.pacebox.core.exception.BusinessException;
import tech.mhuang.pacebox.core.file.FileUtil;
import tech.mhuang.pacebox.core.id.BaseIdeable;
import tech.mhuang.pacebox.core.io.IOUtil;
import tech.mhuang.pacebox.core.util.CollectionUtil;
import tech.mhuang.pacebox.core.util.StringUtil;
import tech.mhuang.pacebox.springboot.core.entity.RequestModel;
import tech.mhuang.pacebox.springboot.core.local.GlobalHeaderThreadLocal;
import tech.mhuang.pacebox.springboot.core.rest.SingleRestTemplate;
import tech.mhuang.pacebox.springboot.protocol.Result;
import tech.mhuang.pacebox.springboot.redis.commands.IRedisExtCommands;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.SequenceInputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.stream.Collectors;
import java.util.zip.CRC32;
import java.util.zip.CheckedInputStream;

/**
 * @description:
 * @author: yf
 * @date: 2023-06-26 14:08:58
 */
@Slf4j
@Repository
public class EvidenceUploadProCommand implements BaseCommand<EvidenceFileProDto, Result> {
    @Autowired
    private Environment environment;
    @Autowired
    private BaseIdeable<String> snowFlake;
    @Autowired
    private IRedisExtCommands redisExtCommands;
    @Autowired
    private SingleRestTemplate singleRestTemplate;
    @Autowired
    EvidenceRecognitionMapper evidenceRecognitionMapper;
    @Autowired
    RedisProperties redisProperties;


    @Override
    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public Result execute(EvidenceFileProDto fileProDto) {
        if (fileProDto.getFile().getOriginalFilename().matches(".*.rar")) {
            return Result.faild("暂不支持改类型压缩包");
        }

        LocalDateTime now = LocalDateTime.now();
        String year = now.getYear() + "";
        String month = now.getMonthValue() + "";
        String day = now.getDayOfMonth() + "";
        String type = "CONDITION";
        String baseDir = getBaseDir(type) + "source/" + year + "/" + month + "/" + day + "/";
        File baseDirFile = new File(baseDir);
        if (!baseDirFile.exists()) {
            baseDirFile.mkdirs();
        }
        if (StringUtil.isNotEmpty(fileProDto.getNo()) && StringUtil.isNotEmpty(fileProDto.getNo())) {
            baseDir = baseDir + fileProDto.getNo() + "/" + fileProDto.getNo() + "/";
            createDirs(baseDir);
        }
        if (fileProDto.getTaskId() == null) {
            fileProDto.setTaskId(snowFlake.generateId());
        }
        try {
            singleUpload(fileProDto.getEvidenceRecognitionRequest(), fileProDto.getChunk(), fileProDto.getChunks(), fileProDto.getTaskId(), fileProDto.getUuid(), fileProDto.getName(), baseDir, fileProDto.getFile());

        } catch (Exception e) {
            throw new BusinessException(Result.SYS_FAILD, "上传文件失败", e);
        }
        return Result.ok();
    }

    /**
     * 文件分片上传，合并逻辑
     *
     * @param evidenceRecognitionRequest
     * @param chunk
     * @param chunks
     * @param taskId
     * @param uuid
     * @param name
     * @param baseDir
     * @param file
     */
    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public void singleUpload(EvidenceRecognitionProRequest evidenceRecognitionRequest, String chunk, String chunks, String taskId, String uuid, String name, String baseDir, MultipartFile file) throws IOException {
        // 分片文件存放包路径
        String targetDir = baseDir + name.split("\\.")[0] + uuid;
        // 分片文件包路径+文件名+文件类型
        String targetFile = baseDir + uuid + "-" + taskId + name;
        // 分片文件对象
        File chunkFile;
        // 总块数
        int chunksIntLabel = 0;
        // 当前块索引
        int chunkIntLabel = 0;
        if (chunks != null) {
            chunksIntLabel = Integer.parseInt(chunks);
        }
        if (chunk != null) {
            chunkIntLabel = Integer.parseInt(chunk);
        }
        boolean isSuccess = false;
        if (taskId != null) {
            // 总块数大于0
            if (chunksIntLabel > 0) {
                File dir = new File(targetDir);
                // 文件包路径创建
                if (!dir.exists()) {
                    dir.mkdir();
                }
                String chunkFileName = taskId + "&&&&&" + chunkIntLabel;
                chunkFile = new File(targetDir, chunkFileName);
            } else {
                chunkFile = new File(targetFile);
            }
            FileOutputStream fs = new FileOutputStream(chunkFile);
            IOUtil.copy(file.getInputStream(), fs, true);
            if (chunksIntLabel > 0) {
                if (mergeFile(targetDir, targetFile, chunksIntLabel)) {
                    isSuccess = true;
                }
            } else {
                isSuccess = true;
            }
        } else {
            return;
        }

        if (isSuccess) {
            String generateId = snowFlake.generateId();
            String floder = fileUpload(generateId, evidenceRecognitionRequest, baseDir, uuid + "-" + taskId + name, name);
            if (StringUtil.isNotEmptyNull(floder)) {
                //写入
                evidenceRecognitionMapper.insertEvidenceRecord(generateId, evidenceRecognitionRequest.getAssetInstBatchId(),
                        generateId, "2", GlobalHeaderThreadLocal.getOrException().getUserId(), "0", generateId);
                if (StringUtil.isEmptyNull(evidenceRecognitionRequest.getType())) {
                    evidenceRecognitionMapper.insertEvidenceRecordAll(generateId, LocalDateTime.now() + "-人类型匹配",
                            "0", "0", GlobalHeaderThreadLocal.getOrException().getUserId(), evidenceRecognitionRequest.getAssetInstBatchId(),
                            "2", "3");
                    //组装数据
                    //查询当前公司下所有的身份证-合同
                    Set<String> keyIds = evidenceRecognitionRequest.getConfigRequests().stream().map(RecognitionConfigRequest::getMatchKey).collect(Collectors.toSet());

                    String actionKey = evidenceRecognitionRequest.getActionKey();
                    String alias = evidenceRecognitionMapper.queryAliasByActionKey(actionKey);
                    keyIds.add(alias);
                    if (CollectionUtil.isNotEmpty(keyIds)) {
                        List<AliasCodeDTO> codes = evidenceRecognitionMapper.queryCodesById(keyIds);
                        List<Map<String, Object>> mapList = evidenceRecognitionMapper.queryContractInfo(codes, evidenceRecognitionRequest.getAssetInstBatchId());
                        //根据key分组
                        log.info("keyIds:{}，alias:{},mapList:{}", keyIds, alias, mapList);
                        Map<String, List<Map<String, Object>>> listMap = mapList.stream().filter(map -> map.get(alias) != null).collect(Collectors.groupingBy(map -> (String) map.get(alias)));
                        List<JSONObject> data = getJsonObjects(listMap);
                        redisExtCommands.hset(6, "ocr_other_data", generateId, data);
                        //证据格式
                        JSONObject evidenceFormat = new JSONObject();
                        evidenceFormat.put("folder", environment.getProperty("file.evidence.url") + floder + File.separator);
                        evidenceFormat.put("uploadFolder", generateId);
                        evidenceFormat.put("matchKey", alias);
                        List<RecognitionConfigRequest> configRequests = evidenceRecognitionRequest.getConfigRequests();
                        int i = 1;
                        List<JSONObject> arrayList = new ArrayList<>();
                        for (RecognitionConfigRequest configRequest : configRequests) {
                            configRequest.setId(i + "");
                            i++;
                            JSONObject ruleData = new JSONObject();
                            ruleData.put("id", configRequest.getId());
                            ruleData.put("mode", configRequest.getMode());
                            ruleData.put("matchKey", configRequest.getMatchKey());
                            ruleData.put("alias", configRequest.getAlias());
                            ruleData.put("aliasMode", configRequest.getAliasMode());
                            ruleData.put("association", configRequest.getAssociation());
                            arrayList.add(ruleData);
                        }
                        evidenceFormat.put("ruleData", arrayList);
                        remoteCall(generateId, evidenceFormat, "");
                    }
                } else {
                    evidenceRecognitionMapper.insertEvidenceRecordAll(generateId, LocalDateTime.now() + "-人类型匹配",
                            "0", "0", GlobalHeaderThreadLocal.getOrException().getUserId(), evidenceRecognitionRequest.getAssetInstBatchId(),
                            "2", "4");
                    //执行种类逻辑
                    Set<String> keyIds = evidenceRecognitionRequest.getConfigRequests().stream().map(RecognitionConfigRequest::getMatchKey).collect(Collectors.toSet());
                    if (CollectionUtil.isNotEmpty(keyIds)) {
                        List<AliasCodeDTO> codes = evidenceRecognitionMapper.queryCodesById(keyIds);
                        List<Map<String, Object>> mapList = evidenceRecognitionMapper.queryContractInfo(codes, evidenceRecognitionRequest.getAssetInstBatchId());
                        JSONObject data = new JSONObject();
                        data.put("match", evidenceRecognitionRequest.getMatchKeyAndData());
                        data.put("data", mapList);
                        redisExtCommands.hset(6, "ocr_other_data2", generateId, data);

                        //证据格式
                        JSONObject evidenceFormat = new JSONObject();
                        evidenceFormat.put("folder", environment.getProperty("file.evidence.url") + floder + File.separator);
                        evidenceFormat.put("uploadFolder", generateId);
                        List<RecognitionConfigRequest> configRequests = evidenceRecognitionRequest.getConfigRequests();
                        int i = 1;
                        List<JSONObject> arrayList = new ArrayList<>();
                        for (RecognitionConfigRequest configRequest : configRequests) {
                            configRequest.setId(i + "");
                            i++;
                            JSONObject ruleData = new JSONObject();
                            ruleData.put("id", configRequest.getId());
                            ruleData.put("mode", configRequest.getMode());
                            ruleData.put("matchKey", configRequest.getMatchKey());
                            arrayList.add(ruleData);
                        }

                        evidenceFormat.put("ruleData", arrayList);
                        remoteCall(generateId, evidenceFormat, "2");
                    }
                }
            }
        }
    }

    @NotNull
    private static List<JSONObject> getJsonObjects(Map<String, List<Map<String, Object>>> listMap) {
        List<JSONObject> data = new ArrayList<>();
        for (Map.Entry<String, List<Map<String, Object>>> listEntry : listMap.entrySet()) {
            JSONObject jsonObject = new JSONObject();
            String match = listEntry.getKey();
            List<Map<String, Object>> childData = listEntry.getValue();
            jsonObject.put("match", match);
            jsonObject.put("data", childData);
            data.add(jsonObject);
        }
        return data;
    }

    private void remoteCall(String id, JSONObject jsonObject, String type) {
        redisExtCommands.hset(6, "ocr_other_before" + type, id, jsonObject);
        RedisMatchDTO dto = new RedisMatchDTO();
        dto.setDb("6");
        dto.setId("1");
        dto.setKey(id);
        dto.setHost(redisProperties.getHost());
        dto.setPort(redisProperties.getPort());
        dto.setPasswd(redisProperties.getPassword());
        dto.setOcrData("ocr_other_data" + type);
        dto.setOcrFile("ocr_other_before" + type);
        dto.setErrorKey("ocr_other_err_key" + type);
        dto.setNotifyKey("ocr_other_notify_key" + type);
        dto.setServerUrl(environment.getProperty("evidence.other.notify_url" + type));
        RequestModel<String> requestModel = new RequestModel<String>() {
        };
        requestModel.setUrl(environment.getProperty("evidence.other.send_url" + type));
        requestModel.setMethod(HttpMethod.POST);
        requestModel.setParams(dto);
        singleRestTemplate.request(requestModel);
    }

    /**
     * 分片上传成功主要逻辑
     *
     * @param uid
     * @param evidenceRecognitionRequest
     * @param baseDir
     * @param uidTaskName
     * @param name
     */
    private String fileUpload(String uid, EvidenceRecognitionProRequest evidenceRecognitionRequest, String baseDir, String uidTaskName, String name) {
        LocalDate now = LocalDate.now();
        String year = String.valueOf(now.getYear());
        String month = String.valueOf(now.getMonthValue());
        String day = String.valueOf(now.getDayOfMonth());
        //系统定义上传文件解析后存放的路径
        String sysUploadUrl = environment.getProperty("file.evidence.url");
        String filePath = evidenceRecognitionRequest.getAssetInstBatchId();
        String resolveUrl = "upload" + "/" + year + "/" + month + "/" + day + "/" + filePath;
        String baseResolveUrl = sysUploadUrl + resolveUrl + File.separator + uid;
        File parentResolveFile = new File(baseResolveUrl);
        if (!parentResolveFile.exists()) {
            parentResolveFile.mkdirs();
        }
        try {
            //保存源文件的上传路径以及原名称
            if (name.matches(".*.zip")) {
                log.info("{}----是压缩文件夹-------", name);
                unZipFile(uid, baseDir, baseResolveUrl, uidTaskName, evidenceRecognitionRequest, resolveUrl + File.separator + uid);
            }
            return resolveUrl;
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            return null;
        }
    }

    /**
     * zip文件解压
     *
     * @param filePath1
     * @param outPath
     * @param fileName
     * @param evidenceRecognitionRequest
     * @param resolveUrl
     */
    private void unZipFile(String uid, String filePath1, String outPath, String fileName, EvidenceRecognitionProRequest evidenceRecognitionRequest, String resolveUrl) {
        String filePath = filePath1 + fileName;
        // 压缩文件的实列,并设置编码
        ZipFile zipFile;
        try {
            zipFile = new ZipFile(filePath, "GBK");
            File file;
            // 获取压缩文中的所有项
            String generateId = snowFlake.generateId();
            for (Enumeration<ZipEntry> enumeration = zipFile.getEntries(); enumeration.hasMoreElements(); ) {
                // 获取元素
                ZipEntry zipEntry = enumeration.nextElement();
                // 排除空文件夹
                if (!zipEntry.getName().endsWith(File.separator)) {
                    String filename = zipEntry.getName();
                    boolean ismkdir = false;
                    // 检查此文件是否带有文件夹
                    if (filename.lastIndexOf("/") != -1) {
                        ismkdir = true;
                    }
                    if (zipEntry.isDirectory()) {
                        // 如果是文件夹则跳过
                        continue;
                    }
                    //源文件名
                    String orinionName = zipEntry.getName().substring(zipEntry.getName().lastIndexOf("/") + 1);
                    //后缀名
                    String replaceFileName;
                    String suffixId = snowFlake.generateId();
                    //替换源文件名后的文件路径
                    replaceFileName = (outPath + "/" + generateId + "_" + zipEntry.getName().replace(orinionName, suffixId + "&" + orinionName)).replace("#", "");
                    file = new File(replaceFileName);
                    if (!file.exists()) {
                        // 如果是目录先创建
                        if (ismkdir) {
                            //目录先创建
                            new File(replaceFileName.substring(0, replaceFileName.lastIndexOf("/"))).mkdirs();
                        }
                        File parentDir = file.getParentFile();
                        if (!file.exists()) {
                            parentDir.mkdirs();
                        }
                    }

                    //创建解压后的文件
                    OutputStream os = Files.newOutputStream(Paths.get(replaceFileName));
                    //带缓的写出流
                    BufferedOutputStream bos = new BufferedOutputStream(os);
                    //读取元素
                    InputStream is = zipFile.getInputStream(zipEntry);
                    //读取流的缓存流
                    BufferedInputStream bis = new BufferedInputStream(is);
                    //检查读取流，采用CRC32算法，保证文件的一致性
                    CheckedInputStream cos = new CheckedInputStream(bis, new CRC32());
                    //字节数组，每次读取1024个字节
                    byte[] b = new byte[1024];
                    // 循环读取压缩文件的值
                    while (cos.read(b) != -1) {
                        // 写入到新文件
                        bos.write(b);
                    }
                    cos.close();
                    bis.close();
                    is.close();
                    bos.close();
                    os.close();
                    saveDataPro(uid, resolveUrl + "/" + generateId + "_" + zipEntry.getName().replace(orinionName, ""), evidenceRecognitionRequest, orinionName, (suffixId + "&" + orinionName).replace("#", ""));
                } else {
                    new File(outPath + zipEntry.getName()).mkdirs();
                }
                log.info("正在解压文件:" + zipEntry.getName());
                log.info(String.valueOf(zipEntry.getName().matches(".*.zip")));
                if (zipEntry.getName().matches(".*.zip")) {
                    log.info(zipEntry.getName() + "是压缩文件-------------------");
                    unZipFile(uid, outPath, outPath, zipEntry.getName(), evidenceRecognitionRequest, resolveUrl);
                }
            }
            log.info("解压完成");
            zipFile.close();
        } catch (Exception e) {
            throw new BusinessException(Result.SYS_FAILD, "解压失败", e);
        }
    }

    public void saveDataPro(String uid, String fileName, EvidenceRecognitionProRequest evidenceRecognitionRequest, String originalName, String replaceFileName) {
        evidenceRecognitionRequest.setFilepath(fileName);
        evidenceRecognitionRequest.setOriginalName(originalName);
        evidenceRecognitionRequest.setReplaceFileName(replaceFileName);
        String loginUser = GlobalHeaderThreadLocal.getOrException().getUserId();
        EvidenceRecognition evidenceRecognitionDO = new EvidenceRecognition();
        evidenceRecognitionDO.setFilepath(fileName);
        evidenceRecognitionDO.setOriginalName(originalName);
        evidenceRecognitionDO.setReplaceFileName(replaceFileName);
        evidenceRecognitionDO.setRecognitionType(5);
        evidenceRecognitionDO.setAssetInstId(evidenceRecognitionRequest.getCompanyId());
        evidenceRecognitionDO.setAssetInstBatchId(evidenceRecognitionRequest.getAssetInstBatchId());
        evidenceRecognitionDO.setCreateUser(loginUser);
        evidenceRecognitionDO.setId(snowFlake.generateId());
        evidenceRecognitionDO.setRecognitionStatus("2");
        evidenceRecognitionMapper.insertEvidenceMapper(evidenceRecognitionDO);

        evidenceRecognitionMapper.insertEvidenceMapperRecord(evidenceRecognitionDO.getId(), uid,
                evidenceRecognitionDO.getId(), loginUser);
    }

    /**
     * 合并文件
     *
     * @param fileDir
     * @param fileName
     * @param chunks
     * @return
     */
    private boolean mergeFile(String fileDir, String fileName, Integer chunks) {
        if (isAllUploaded(fileDir, chunks)) {
            try {
                List<FileInputStream> targetList = new CopyOnWriteArrayList<>();
                File parentFile = new File(fileDir);
                File[] files = parentFile.listFiles();
                for (int i = 0; i < chunks; i++) {
                    for (File file : files) {
                        if (file.getName().contains("&&&&&" + i)) {
                            FileInputStream sourceStream = new FileInputStream(file);
                            targetList.add(sourceStream);
                            break;
                        }
                    }
                }

                Enumeration<FileInputStream> targetEnum = Collections.enumeration(targetList);
                SequenceInputStream sequenceStream = new SequenceInputStream(targetEnum);
                // 合分片文件
                saveStreamToFile(sequenceStream, fileName);

                for (File file : files) {
                    if (file.isDirectory()) {
                        FileUtil.deleteDirectory(file);
                    } else {
                        FileUtil.delete(file);
                    }
                }
                FileUtil.deleteDirectory(parentFile);
                return true;
            } catch (Exception e) {
                log.error(e.getMessage());
                return false;
            }
        }
        return false;
    }


    /**
     * 基本路径获取
     *
     * @param type
     * @return
     */
    private String getBaseDir(String type) {
        String path = null;
        try {
            if (type != null) {
                String dir = environment.getProperty("file.evidence.url");
                File baseDir = new File(dir);
                if (!baseDir.exists()) {
                    baseDir.mkdirs();
                }
                path = dir;
            }
        } catch (Exception e) {
            throw new BusinessException(500, "上传类型错误");
        }
        return path;
    }

    /**
     * 创建基础存放路径包
     *
     * @param baseDir
     */
    private void createDirs(String baseDir) {
        File baseDirFile = new File(baseDir);
        if (!baseDirFile.exists()) {
            baseDirFile.mkdirs();
        }
    }

    /**
     * 判断是否全部上传成功
     *
     * @param fileDir
     * @param chunk
     * @return
     */
    private Boolean isAllUploaded(String fileDir, int chunk) {
        File file = new File(fileDir);
        File[] files = file.listFiles();
        int fileLength = files.length;
        return fileLength == chunk;
    }

    /**
     * 合并分片文件
     *
     * @param inputStream
     * @param filePath
     * @throws Exception
     */
    private void saveStreamToFile(SequenceInputStream inputStream, String filePath) throws Exception {
        /* 创建输出流，写入数据，合并分块 */
        OutputStream outputStream = Files.newOutputStream(Paths.get(filePath));
        IOUtil.copy(inputStream, outputStream, true);
    }
}
